#include "simulation.h"

#include <algorithm>
#include <queue>
#include <stdlib.h>

#include "gamestate.h"
#include "pathfinder.h"
#include "setmath.h"

#include "entities/army.h"
#include "entities/building.h"
#include "entities/city.h"
#include "entities/improvement.h"
#include "entities/tile.h"
#include "entities/unit.h"

#include "renderer/renderer.h"

Simulation* simulation;

Simulation::Simulation(){
}

void Simulation::Initialise(){
    gamestate->CreateMap();
    SetTileWaterDistance();

    gamestate->CreatePlayer(false);
    gamestate->CreatePlayer(true);

    City* city = CreateCity(84, 1);
    gamestate->GetBuilding(city->GetPrimaryBuildingID())->SetTurnsToComplete(0);
    City* city2 = CreateCity(103, 1);
    gamestate->GetBuilding(city2->GetPrimaryBuildingID())->SetTurnsToComplete(0);

    Army* army = gamestate->CreateArmy();
    army->SetTurnsToComplete(0);
    army->SetIndex(98);
    army->SetOwner(0);

    int unitID = gamestate->CreateUnit("worker")->GetID();

    army->AddToUnits(unitID);

    simulation_state = AWAITING_ORDERS;

    SetTilesToFarmland();

}

void Simulation::ProcessOrder(int target_id, int mouse_button){
    Entity* target = gamestate->GetEntity(target_id);
    if(!target){
        std::cout<<"No entity clicked\n";
        return;
    }

    if(mouse_button == GLFW_MOUSE_BUTTON_1){
        SelectEntity(target->GetID());
    }
    else if(mouse_button == GLFW_MOUSE_BUTTON_2){
        GiveOrder(target);
    }
}

void Simulation::ChangeImprovementConstructionPriority(int improvement_id){
}

void Simulation::SelectEntity(int id){
    gamestate->SelectEntity(id);
}

// Sets new state
void Simulation::GiveOrder(Entity* target){
    gamestate->SetActiveEntity(gamestate->GetSelectedEntityID());
    Entity* actor = gamestate->GetActiveEntity();
    if(!actor){
        std::cout<<"No active entity\n";
        return;
    }

    EntityType target_type = target->GetType();

    if(actor->GetType() != ET_ARMY){
        std::cout<<"Not an army\n";

        if(actor->GetUnitIDs().size() == 0){
            std::cout<<"No units to create an army with\n";
            return;
        }
        Army* army = gamestate->CreateArmy();
        army->SetIndex(actor->GetIndex());
        TransferUnits(actor, army);
        actor = army;
        gamestate->SetActiveEntity(actor->GetID());
        SelectEntity(actor->GetID());
    }
    if(target_type == ET_INVALID or target_type == ET_UNIT){
        std::cout<<"Invalid army target\n";
        return;
    }

    if(target_type == ET_TILE){
        simulation_state = MOVING;
    }
    if(target_type == ET_CITY){
        simulation_state = GARRISONING;
    }

    move_path = pathfinder.GetPath(actor->GetIndex(), target->GetIndex());
}

void Simulation::Update(){
    switch(simulation_state){
        case MOVING:{
            Army* army = static_cast<Army*>(gamestate->GetActiveEntity());
            MoveArmy(army);
            break;
        }
        case GARRISONING:{
            Army* army = static_cast<Army*>(gamestate->GetActiveEntity());
            if(move_path.size() == 1){
                int garrison_index = move_path.back();
                auto garrison = gamestate->GetImprovementByIndex(garrison_index);
                GarrisonArmy(army, garrison);
                army = nullptr;
                simulation_state = AWAITING_ORDERS;
            }
            else{
                MoveArmy(army);
            }
            break;
        }
        default:{
            break;
        }
    }
}

void Simulation::MoveArmy(Army* army){
    army->SetIndex(move_path.back());
    move_path.pop_back();
    if(move_path.size() == 0){
        simulation_state = AWAITING_ORDERS;
    }
}

void Simulation::GarrisonArmy(Army* army, Entity* garrison){
    garrison->AddToUnits(army->GetUnitIDs());
    gamestate->DeleteArmy(army->GetID());
    army = nullptr;

    if(gamestate->GetSelectedEntityID() == gamestate->GetActiveEntityID()){
        SelectEntity(-1);
    }
    gamestate->SetActiveEntity(-1);
}

void Simulation::TransferUnits(Entity* donor, Entity* reciever){
    reciever->AddToUnits(donor->GetUnitIDs());
    donor->RemoveAllUnits();
}

// END OF TURN /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void Simulation::EndTurn(){
    EOTArmies();
    EOTImprovements();
    EOTCities();

    gamestate->IncrementTurn();

    renderer->RefreshTerrain();
}

void Simulation::EOTArmies(){
}

void Simulation::EOTImprovements(){
    for(Improvement* improvement : gamestate->GetAllImprovements()){
//        if(improvement->GetOwner() == gamestate->GetActivePlayerID()){
            improvement->EndTurn();
        }
//    }
}

void Simulation::EOTCities(){
    for(City* city : gamestate->GetAllCities()){
        if(city->GetOwner() != gamestate->GetActivePlayerID()){
            continue;
        }
        city->EndTurn();
        if(city->GrowPopulation()){
            std::set<int> neighbours = gamestate->GetTileIndicesBorderingProvince(city->GetID());
            std::set<int> tiles_in_admin_range = gamestate->GetTileIndicesWithinDistance(city->GetIndex(), city->GetAdministrativeDistance());
            std::set<int> neighbours_in_admin_range = SetMath::AND(tiles_in_admin_range, neighbours);
            std::set<int> free_neighbours_in_admin_range;
            for(int neighbour : neighbours_in_admin_range){
                if(gamestate->GetProvinceIDContainingTile(neighbour) == -1){
                    free_neighbours_in_admin_range.insert(neighbour);
                }
            }
            AddTileToProvince(city->GetID(), PickMostValuableTile(free_neighbours_in_admin_range));
        }
        city->DoImprovementConstruction();
        city->DoBuildingConstruction();
    }
    SetTilesToFarmland();
}
// Tiles ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @TODO slow, YAY nested fors
void Simulation::SetTileWaterDistance(){
    for(auto edge:gamestate->GetRivers().GetAllEdges()){
        int a;
        std::tie(a, std::ignore, std::ignore) = edge;
        std::set<int> adjacent_tiles = gamestate->GetTileIndicesWithinDistance(a, 3);

        for(int index : adjacent_tiles){
            Tile* tile = gamestate->GetTileByIndex(index);
            if(tile){
                tile->SetMinimumDistanceToFreshWater(gamestate->Distance(tile->GetIndex(), a));
            }
        }
    }
}

void Simulation::SetTilesToFarmland(){
    std::vector<City*> cities = gamestate->GetAllCities();
    for(City* city : cities){
        city->UpdateFarms();
    }
}

void Simulation::ClaimTilesWithinDistance(City* city, float distance){
    for(int index : gamestate->GetTileIndicesWithinDistance(city->GetIndex(), distance)){
        Tile* tile = gamestate->GetTileByIndex(index);
        if(gamestate->GetProvinceIDContainingTile(index) == -1){
            tile->SetOwner(city->GetOwner());
            AddTileToProvince(city->GetID(), index);
        }
    }
}

int Simulation::PickMostValuableTile(const std::set<int> & indices){
    int highest_index = -1;
    int highest_value = -1;

    for(int index : indices){
        Tile* tile = gamestate->GetTileByIndex(index);
        if(tile->GetFood() > highest_value){
            highest_index = index;
            highest_value = tile->GetFood();
        }
    }
    return highest_index;
}


// CREATERS ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
City* Simulation::CreateCity(int index, int owner){
    City* city;
    Tile* tile;

    city = gamestate->CreateCity(index, owner);
    gamestate->GetBuilding(city->GetPrimaryBuildingID())->SetTurnsToComplete(0);

    ClaimTilesWithinDistance(city, 1.5);

    tile = gamestate->GetTileByIndex(index);
    tile->SetTerrain("citycenterground", "none");

    return city;
}

void Simulation::Build(std::string name){
    Improvement* improvement = gamestate->GetImprovement(gamestate->GetSelectedEntityID());
    if(!improvement){
        improvement = gamestate->GetCity(gamestate->GetSelectedEntityID());
    }
    if(!improvement){
        std::cout<<"Improvement invalid: "<<gamestate->GetSelectedEntityID()<<std::endl;
        return;
    }

    /// @TODO Check for resource availability

    Building* building = gamestate->CreateBuilding(name);

    if(building==nullptr){
        std::cout<<"Warning: Simulation::BuildBuilding failed to get building from gamestate: "<<name<<std::endl;
        return;
    }

    building->SetOwner(gamestate->GetActivePlayerID());
    improvement->AddBuildingToConstructionQueue(building->GetID());
}

void Simulation::BuildUnit(std::string name, int builder_id){
    Improvement* imp = gamestate->GetImprovement(builder_id);
    Unit* unit;

    if(!imp){
        if(!(imp = gamestate->GetCity(builder_id))){
            std::cout<<"Simulation::BuildUnit Error: Invalid builder city id: "<<builder_id<<std::endl;
            return;
        }
    }

    unit = gamestate->CreateUnit(name);
    unit->SetOwner(imp->GetOwner());

    imp->AddUnitToRecruitmentQueue(unit->GetID());
}

void Simulation::CreateHoverImprovement(std::string building_name){
    Improvement* improvement = gamestate->CreateImprovement();
    Building* building = gamestate->CreateBuilding(building_name);
    City* city = gamestate->GetCity(gamestate->GetSelectedEntityID());

    improvement->AddBuildingToConstructionQueue(building->GetID());
    improvement->SetCityOwner(city->GetID());
    improvement->SetOwner(city->GetOwner());

    gamestate->SetHoverImprovementID(improvement->GetID());
}

void Simulation::MoveHoverImprovement(int tile_id){
    Tile* tile = gamestate->GetTileByID(tile_id);
    Improvement* imp = gamestate->GetHoverImprovement();
    if(!imp){
        return;
    }
    std::set<int> province = gamestate->GetProvince(imp->GetCityOwner());
    if(province.find(tile->GetIndex()) != province.end()){
        imp->SetValidPosition(true);
    }
    else{
        imp->SetValidPosition(false);
    }
    imp->SetIndex(tile->GetIndex());
}

bool Simulation::PlaceImprovement(int imp_id){
    Improvement* imp = gamestate->GetImprovement(imp_id);
    City* city = gamestate->GetCity(imp->GetCityOwner());
    Tile* tile = gamestate->GetTileByIndex(imp->GetIndex());

    if(!imp->IsInAValidPosition()){
        return false;
    }
    if(!tile){
        return false;
    }
    if(tile->HasImprovement()){
        return false;
    }
    tile->SetImprovement(imp_id);
    city->AppendImprovementConstructionQueue(imp->GetID());
    simulation->CreateRoad(city->GetIndex(), imp->GetIndex());
    renderer->RefreshTerrain();
    return true;
}

void Simulation::DeleteImprovement(int id){
    Improvement* imp = gamestate->GetImprovement(id);
    std::set<int> units = imp->GetUnitIDs();
    std::set<int> buildings = imp->GetBuildingIDs();

    Army* army = gamestate->CreateArmy();
    army->SetIndex(imp->GetIndex());
    army->AddToUnits(units);

    for(int building_id:buildings){
        gamestate->DeleteBuilding(building_id);
    }

    gamestate->DeleteImprovement(id);
}

bool Simulation::IsValidGarrisonLocation(Tile* tile, int player){
    if(tile->GetImprovementID()!=-1){
        Improvement* improvement = gamestate->GetImprovement(tile->GetImprovementID());

        if(improvement->GetOwner() == player and improvement->CanContainUnits()){
            return true;
        }
    }
    return false;
}

bool Simulation::IsTileInAProvince(int tile_index){
    return (gamestate->GetProvinceIDContainingTile(tile_index) != -1) ? true : false;
}

void Simulation::AddTileToProvince(int province_id, int tile_index){
    if(gamestate->IsValidIndex(tile_index) == false){
        return;
    }
    gamestate->AddTileToProvince(province_id, tile_index);
}

void Simulation::TestRoadCreation(){
    for(unsigned int i=0; i<16*16; i++){
        if(i==84){
            continue;
        }
        CreateRoad(84, i);
    }
    renderer->RefreshTerrain();
}

bool Simulation::CreateRoad(int begin, int end, std::string roadType){
    std::vector<int> path = pathfinder.GetPath(begin, end, true);

    if(path.size()==0){
        std::cout<<"Simulation::CreateRoad Error: Pathfinder returned zero length path between "<<begin<<" and "<<end<<std::endl;
        return false;
    }

    for(unsigned int i=0;i<path.size();i++){
        gamestate->GetTileByIndex(path.at(i))->SetRoadType(roadType);
    }

    gamestate->AddPathToRoadNetwork(path, 1.5);

    return true;
}
